#include<stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<dirent.h>
#include<stdlib.h>

#define BUFFER_SIZE 4096
#define PATH_MAX 4096

/**
 * 复制普通文件
 * @param src      源文件路径
 * @param dest     目标文件路径
 * @param stat_buf 源文件属性结构体指针
 * @return 成功:0  失败:-1
 * 相关函数:open() close() read() write() futimens()
 * 
 * int futimens(int fd, const struct timespec times[2]) //修改文件的时间戳
 * 返回值:成功:0  失败:-1
 * 例:struct timespec times[2] = {stat.st_atim,stat.st_mtim} //1.访问时间  2.修改时间
 * 
*/
int copy_file(const char *src, const char *dest, const struct stat *stat_buf)
{
    int src_fd = -1,dest_fd = -1;
    ssize_t bytes_read;
    char buffer[BUFFER_SIZE];

    //1.以只读方式打开源文件
    if((src_fd = open(src, O_RDONLY)) == -1)
    {
        perror("打开源文件失败");
        return -1;
    }

    //2.创建目标文件并打开(保留原始权限，不存在则创建，并清空已存在内容)
    if((dest_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, stat_buf->st_mode)) == -1){
        perror("创建目标文件失败");
        close(src_fd);
        return -1;
    }

    //3.复制文件内容
    while((bytes_read = read(src_fd,buffer,BUFFER_SIZE)) > 0)
    {
        if(write(dest_fd,buffer,bytes_read) != bytes_read)
        {
            perror("写入文件内容失败,文件复制出错\n");
            close(src_fd);
            close(dest_fd);
            return -1;
        }
    }

    //4.保留文件时间搓(访问时间和修改时间)
    struct timespec times[2] = {
        stat_buf->st_atim,   //访问时间
        stat_buf->st_mtim    //修改时间
    };

    if(futimens(dest_fd ,times) == -1)
    {
        perror("WARN:保留时间戳失败");
        //不影响整体复制结果，仅打印警告
    }

    //5.关闭文件描述符
    close(src_fd);
    close(dest_fd);
    return 0;
}


/**
 * 复制符号链接
 * @param src  源文件路径
 * @param dest 目标文件路径
 * @return 成功:0 失败:-1
 * 
 * 相关函数:readlink() symlink()
 * 
 * ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);//读取符号链接指向的目标路径
 * 参数 pathname:符号链接路径
 *      buf:用于存储符号链接指向的目标路径
 *      bufsiz:最大存储的字节数
 * 返回值:目标路径的字符串长度
 * 
 * int symlink(const char *target, const char *linkpath);//创建符号链接
 * 参数 target:源文件路径
 *      linkpath:要创建的符号链接路径
 * 返回值:成功:0 失败:-1
*/
int copy_symlink(const char *src, const char *dest)
{
    char link_target[PATH_MAX];
    ssize_t target_len;

    //1.读取符号链接指向的目标路径
    if((target_len == readlink(src , link_target, sizeof(link_target)-1) )== -1){
        perror("读取链接目标失败");
        return -1;
    }
    link_target[target_len] = '\0';//确保字符串终止

    //2.创建新的符号链接
    if(symlink(link_target , dest) == -1)
    {
        perror("创建符号链接失败");
        return -1;
    }

    return 0;
}


/**
 * 复制目录
 * @param src_path  源目录路径
 * @param dest_path 目标目录路径
 * @return 成功:0 失败:-1
 * 相关函数:lstat() opendir() closedir() mkdir() readdir() 
*/
int copy_directory(const char *src_path,const char *dest_path)
{
    DIR *dir;                    //目录指针 
    struct dirent *ep;           //目录项指针
    struct stat stat_buf;        //文件属性结构体

    //1.获取源目录元数据
    if(lstat(src_path,&stat_buf) == -1){
        perror("获取目录信息失败");
        return -1;
    }

    //2.打开源目录
    if((dir = opendir(src_path)) == NULL)
    {
        perror("打开目录失败");
        return -1;
    }

    //3.创建目标目录
    if(mkdir(dest_path,stat_buf.st_mode)==-1)
    {
        if(errno != EEXIST){
            perror("创建目录失败");
            return -1;
        }
    }
    //getchar();//阻塞

    //4.遍历目录项
    while((ep = readdir(dir)) != NULL)
    {
        //4.1跳过特殊目录项 "."和".." :当前以及上一级
        if(strcmp(ep->d_name,".") == 0 || strcmp(ep->d_name,"..") == 0)
        {
            continue;
        }
        
        //4.2构建完整路径(使用安全格式化函数)
        char src_full[PATH_MAX],dest_full[PATH_MAX];

        snprintf(src_full , PATH_MAX, "%s/%s", src_path , ep->d_name);
        snprintf(dest_full, PATH_MAX, "%s/%s", dest_path, ep->d_name);
        //4.3获取文件属性
        if(lstat(src_full,&stat_buf)==-1){
            perror("获取文件属性失败");
            closedir(dir);
            return -1;
        }

        //4.4根据文件类型分发处理逻辑
        switch(stat_buf.st_mode & S_IFMT)
        {
            case S_IFREG:     //普通文件
                printf("复制文件:%s -> %s\n",src_full,dest_full);
                if(copy_file(src_full,dest_full,&stat_buf) == -1)
                {
                    closedir(dir);
                    return -1;
                }
                break;
            case S_IFDIR:   //子目录
                printf("创建子目录:%s\n",dest_full);
                if(copy_directory(src_full,dest_full) == -1)
                {
                    closedir(dir);
                    return -1;
                }
                break;

            case S_IFLNK:  //符号链接
                printf("复制符号链接:%s -> %s\n",src_full,dest_full);
                if(copy_symlink(src_full,dest_full) == -1)
                {
                    closedir(dir);
                    return -1;
                }
            
            default:       //其它类型(设备文件、管道)
                fprintf(stderr,"WARN:跳过特殊文件:%s\n",src_full);
                break;
        }
    }

    //5.关闭文件流
    closedir(dir);
    return 0;
}

int main(int argc, char const *argv[])
{
    if(argc != 3)
    {
        fprintf(stderr,"格式:%s dirpath1 dirpath2\n",argv[0]);
        return -1;
    }

    //执行目录复制
    if(copy_directory(argv[1],argv[2])==-1)
    {
        fprintf(stderr,"errno:文件复制失败\n");
        exit(EXIT_FAILURE);
    }
    printf("successful:目录已完整复制至 %s\n",argv[2]);

    return 0;
}
